package data;

import java.util.Iterator;
import java.util.TreeSet;

public class Edge {

	private Location from;
	
	private Location to;
	
	private float distance;

	public Edge() {};
	
	public Edge(Location from, Location to, float distance){
		this.from = from;
		this.to = to;
		this.distance = distance;
	}
	
	public Location getFrom() {
		return from;
	}

	public void setFrom(Location from) {
		this.from = from;
	}

	public Location getTo() {
		return to;
	}

	public void setTo(Location to) {
		this.to = to;
	}

	public float getDistance() {
		return distance;
	}

	public void setDistance(float distance) {
		this.distance = distance;
	}
	
	public double getNearestDistance(Edge ed){
		double gradient1 = this.getGradient();
		double gradient2 = ed.getGradient();
		double interception1 = this.getInterception();
		double interception2 = ed.getInterception();
		double sliceX = (interception1 - interception2) / (gradient2 - gradient1);
		if ( (this.getTo().getLongitude() < sliceX && sliceX < this.getFrom().getLongitude()) || (this.getTo().getLongitude() > sliceX && sliceX > this.getFrom().getLongitude()) ) {
			return (0.0);
		}
		else{
			TreeSet<Double> distances = retrieveDistances(ed);
			return distances.first();
		}
	}

	public double getFarestDistance(Edge ed){
		TreeSet<Double> distances = retrieveDistances(ed);
		return distances.last();
	}
	
	public double getAverageDistance(Edge ed){
		TreeSet<Double> distances = retrieveDistances(ed);
		Iterator<Double> distIt = distances.iterator();
		double avg = 0.0;
		int cnt = 0;
		while (distIt.hasNext()){
			avg = (avg * cnt + distIt.next())/(cnt+1);
			cnt++;
		}
		return avg;
	}

	private TreeSet<Double> retrieveDistances(Edge ed) {
		double gradient1;
		double gradient2;
		double interception1;
		double interception2;
		double sliceX;
		gradient1 = this.getGradient();
		gradient2 = ed.getGradient();
		interception1 = this.getInterception();
		interception2 = ed.getInterception();
		double interceptionFrom1Per2 = this.getFrom().getLatitude() - this.getFrom().getLongitude() * (1/gradient2);
		double interceptionFrom2Per1 = ed.getFrom().getLatitude() - ed.getFrom().getLongitude() * (1/gradient1);
		double interceptionTo1Per2 = this.getTo().getLatitude() - this.getTo().getLongitude() * (1/gradient2);
		double interceptionTo2Per1 = ed.getTo().getLatitude() - ed.getTo().getLongitude() * (1/gradient1);
		TreeSet<Double> distances = new TreeSet<Double>();
		sliceX = (interceptionFrom1Per2 - interception2) / (gradient2 - (1/gradient2));
		if ( (ed.getTo().getLongitude() < sliceX && sliceX < ed.getFrom().getLongitude()) || (ed.getTo().getLongitude() > sliceX && sliceX > ed.getFrom().getLongitude()) ) {
			double latSlice = interceptionFrom1Per2 + (1 / gradient2) * sliceX;
			double latDist = Math.abs(latSlice - this.getFrom().getLatitude());
			double longDist = Math.abs(sliceX - this.getFrom().getLongitude());
			double dist = Math.sqrt(latDist*latDist+longDist*longDist);
			distances.add(dist);
		}
		sliceX = (interceptionFrom2Per1 - interception1) / (gradient1 - (1/gradient1));
		if ( (this.getTo().getLongitude() < sliceX && sliceX < this.getFrom().getLongitude()) || (this.getTo().getLongitude() > sliceX && sliceX > this.getFrom().getLongitude()) ) {
			double latSlice = interceptionFrom2Per1 + (1 / gradient1) * sliceX;
			double latDist = Math.abs(latSlice - ed.getFrom().getLatitude());
			double longDist = Math.abs(sliceX - ed.getFrom().getLongitude());
			double dist = Math.sqrt(latDist*latDist+longDist*longDist);
			distances.add(dist);
		}
		sliceX = (interceptionTo1Per2 - interception2) / (gradient2 - (1/gradient2));
		if ( (ed.getTo().getLongitude() < sliceX && sliceX < ed.getFrom().getLongitude()) || (ed.getTo().getLongitude() > sliceX && sliceX > ed.getFrom().getLongitude()) ) {
			double latSlice = interceptionTo1Per2 + (1 / gradient2) * sliceX;
			double latDist = Math.abs(latSlice - this.getTo().getLatitude());
			double longDist = Math.abs(sliceX - this.getTo().getLongitude());
			double dist = Math.sqrt(latDist*latDist+longDist*longDist);
			distances.add(dist);
		}
		sliceX = (interceptionTo2Per1 - interception1) / (gradient1 - (1/gradient1));
		if ( (this.getTo().getLongitude() < sliceX && sliceX < this.getFrom().getLongitude()) || (this.getTo().getLongitude() > sliceX && sliceX > this.getFrom().getLongitude()) ) {
			double latSlice = interceptionTo2Per1 + (1 / gradient1) * sliceX;
			double latDist = Math.abs(latSlice - ed.getTo().getLatitude());
			double longDist = Math.abs(sliceX - ed.getTo().getLongitude());
			double dist = Math.sqrt(latDist*latDist+longDist*longDist);
			distances.add(dist);
		}
		double latDist = Math.abs(this.getTo().getLatitude() - ed.getTo().getLatitude());
		double longDist = Math.abs(this.getTo().getLongitude() - ed.getTo().getLongitude());
		double dist = Math.sqrt(latDist*latDist+longDist*longDist);
		distances.add(dist);
		latDist = Math.abs(this.getTo().getLatitude() - ed.getFrom().getLatitude());
		longDist = Math.abs(this.getTo().getLongitude() - ed.getFrom().getLongitude());
		dist = Math.sqrt(latDist*latDist+longDist*longDist);
		distances.add(dist);
		latDist = Math.abs(this.getFrom().getLatitude() - ed.getTo().getLatitude());
		longDist = Math.abs(this.getFrom().getLongitude() - ed.getTo().getLongitude());
		dist = Math.sqrt(latDist*latDist+longDist*longDist);
		distances.add(dist);
		latDist = Math.abs(this.getFrom().getLatitude() - ed.getFrom().getLatitude());
		longDist = Math.abs(this.getFrom().getLongitude() - ed.getFrom().getLongitude());
		dist = Math.sqrt(latDist*latDist+longDist*longDist);
		distances.add(dist);
		return distances;
	}
	

	public double getGradient(){
		return (this.getFrom().getLatitude()-this.getTo().getLatitude())
		/ (this.getFrom().getLongitude()-this.getTo().getLongitude());
	}
	
	public double getAngle(){
		boolean test = (AggregatedEdgeMap.compareDouble(51.323371887, this.getTo().getLatitude())==0 || AggregatedEdgeMap.compareDouble(51.32327652, this.getFrom().getLatitude())==0);
		double angle = Math.toDegrees(
				Math.atan(
						(
						this.getEuDistance()*this.getEuDistance()
						+ this.getLatDistance()*this.getLatDistance()
						- this.getLongDistance()*this.getLongDistance()
						) / (
								2 * this.getEuDistance() * this.getLatDistance()
								)
								)
								);
		angle = Math.toDegrees(Math.atan(this.getLatDistance()/this.getLongDistance()));
		double origangle = angle;				

		if (this.getTo().getLatitude() > this.getFrom().getLatitude()){
			if (this.getTo().getLongitude() > this.getFrom().getLongitude()){
				//correction on angle in order to have full 360 degrees: 90 minus angle
				angle = 90 - angle;
			}
			else{
				//270 plus angle
				angle = 270 + angle;
			}
		}
		else{
			if (this.getTo().getLongitude() > this.getFrom().getLongitude()){
				//180 minus angle
				angle = 90 + angle;
			}
			else{
				//180 plus angle
				angle = 270 - angle;
			}
		}
		return angle; 
	}
	
	
	private double getLatDistance() {
		return Math.abs(this.getTo().getLatitude() - this.getFrom().getLatitude());
	}

	private double getLongDistance() {
		return Math.abs(this.getTo().getLongitude() - this.getFrom().getLongitude());
	}

	public double getInterception(){
		return this.getFrom().getLatitude() - this.getFrom().getLongitude() * this.getGradient();
	}
	
	public String toString() {
		return this.getFrom() + "->" + this.getTo();
	}
	
	public Double getEuDistance() {
		return this.getFrom().getDistanceTo(this.getTo());
	}
	

}
